#include "material.h"
#include "perlin_noise.h"
#include <math.h>
#include <stdio.h>

#define EPSILON 0.001

PhongMaterial::PhongMaterial(const d_Vec3f &diffuseCol, const d_Vec3f &specularCol, float expon, const d_Vec3f &reflectiveCol, const d_Vec3f &transparentColor, float indexOfRefraction, d_Mat4f *trans , char* texture = NULL, char* bumpmap = NULL)
{
	diffuseColor = diffuseCol;
	specularColor = specularCol;
	exponent = expon;
	reflectiveColor = reflectiveCol;
	this->transparentColor = transparentColor;
	if(texture != NULL)
		this->texture = this->texture->LoadTGA(texture);
	else
		this->texture = NULL;
	if(trans!=NULL)
	{
		this->trans = *trans;
	}
	else
	{
		//cout << "transfiriendo" << endl;
		this->trans = d_Mat4f();
		this->trans.diagonal(1);
	}
	if(bumpmap!=NULL)
		this->bumpmap = this->bumpmap->LoadTGA(bumpmap);
	else
		this->bumpmap = NULL;
	refractionIndex = indexOfRefraction;
}

d_Vec3f PhongMaterial::Shade(const Ray &ray, const Hit &hit, const d_Vec3f &dirToLight, const d_Vec3f &lightColor) const
{
	d_Vec3f normal = hit.getNormal();
	
	///////////////////
	d_Vec3f diffuse;
	//int i=0, j=0;
	float theta=0;
	int size;
	if(texture!=NULL)
		size = texture->Width()-1;
	else 
		if(bumpmap!=NULL)
			size = bumpmap->Width()-1;
	
	d_Mat4f mattmp = trans;
	d_Vec4f pointtmp = mattmp*d_Vec4f(ray.pointAtParameter(hit.getT()),0);
	d_Vec3f point = norm(d_Vec3f(pointtmp[0],pointtmp[1],pointtmp[2]));


	if(point[0]>=0)
		theta = 3.141592 -  atan (point[2]/point[0]);
	else
		theta = atan (-point[2]/point[0]);
	float phi = 3.151592-acos(point[1]);

	float d = dot(dirToLight, normal);
	float vrq;
	if(d< 0)
		d = 0;
	
	float proof= len(dirToLight-ray.getDirection());

	float tmp = dot(normal, (dirToLight-ray.getDirection())/proof);//dot(-ray.getDirection(), norm(2*d*hit.getNormal()-dirToLight));
	if(tmp<0)
		tmp = 0;
	vrq = pow(tmp , exponent);

    	if(texture==NULL)
		diffuse = getDiffuseColor(ray.pointAtParameter(hit.getT()));
	//cout << d << endl;
	return (diffuse*d + getSpecularColor()*vrq)*lightColor;


}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

CookTorranceMaterial::CookTorranceMaterial(const d_Vec3f &diffuseCol, const d_Vec3f &specularCol, float al, float rough, const d_Vec3f &reflectiveCol, const d_Vec3f &transparentColor, d_Mat4f *trans)
{
	diffuseColor = diffuseCol;
	specularColor = specularCol;
	alpha = al;
	roughness = rough;
	reflectiveColor = reflectiveCol;
	this->transparentColor = transparentColor;
	if(trans!=NULL)
	{
		this->trans = *trans;
	}
	else
	{
		//cout << "transfiriendo" << endl;
		this->trans = d_Mat4f();
		this->trans.diagonal(1);
	}
}
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define maxdot(a,b) max(0,dot(a,b))

d_Vec3f CookTorranceMaterial::Shade(const Ray &ray, const Hit &hit, const d_Vec3f &dirToLight, const d_Vec3f &lightColor) const
{
	d_Vec3f E = ray.getDirection()*(-1);
	d_Vec3f L = dirToLight;
	d_Vec3f N = hit.getNormal();
	d_Vec3f H = norm(E+L);
	
	float LdotH = max(0,dot(L,H));
	float HdotN = max(0,dot(H,N));
	float LdotN = max(0,dot(L,N));
	float EdotH = max(0,dot(E,H));
	float EdotN = max(0,dot(E,N));
	float LdotV = max(0,dot(L,E));
	
	if(LdotN<0)
		return d_Vec3f(0,0,0);

	float ks = 2;
	float F = ks + (1-ks)*pow(1-LdotH,5);
	
	float T1 = (2*HdotN*EdotN)/EdotH;
	float T2 = (2*HdotN*LdotN)/EdotH;
	float G = min(1, min(T1,T2));
	
	float roughness_a = 1.0f / ( pow(roughness,2) * pow( HdotN, 4 ) );
    float roughness_b = HdotN*HdotN - 1.0f;
    float roughness_c = pow(roughness,2) * pow(HdotN,2);
 
	float D = roughness_a * exp( roughness_b / roughness_c );
	
	float Rs;// = (D*F*G)/(EdotN*LdotN);
	if(EdotN*LdotN<=0)
		Rs = 0;
	else
		Rs = (D*F*G)/(EdotN*LdotN);
	
	return ( getSpecularColor()*Rs + getDiffuseColor(ray.pointAtParameter(hit.getT())) ) *LdotN;
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

Checkerboard::Checkerboard(d_Mat4f *m, Material *mat1, Material *mat2)
{
	trans = *m;
	this->mat1 = mat1;
	this->mat2 = mat2;
}

bool odd(int x)
{
	if((x+1)%2==0)
		return true;
	else 
		return false;
}

d_Vec3f Checkerboard::Shade(const Ray &ray, const Hit &hit, const d_Vec3f &dirToLight, const d_Vec3f &lightColor) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(ray.pointAtParameter(hit.getT()),1);
	if((!odd((int)floor(p[0]))&&!odd((int)floor(p[1])))||(odd((int)floor(p[0]))&&odd((int)floor(p[1]))))
	{
		if(!odd((int)trunc(floor(p[2]))))
			return mat1->Shade(ray,hit,dirToLight,lightColor);
		else
			return mat2->Shade(ray,hit,dirToLight,lightColor);
	}
	else
	{
		if(!odd((int)trunc(floor(p[2]))))
			return mat2->Shade(ray,hit,dirToLight,lightColor);
		else
			return mat1->Shade(ray,hit,dirToLight,lightColor);
	}
	
}

Noise::Noise(d_Mat4f *m, Material *mat1, Material *mat2, int octaves)
{
	trans = *m;
	this->mat1 = mat1;
	this->mat2 = mat2;
	this->octaves = octaves;
}

d_Vec3f Noise::Shade(const Ray &ray, const Hit &hit, const d_Vec3f &dirToLight, const d_Vec3f &lightColor) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(ray.pointAtParameter(hit.getT()),1);
	double t = (PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves)+.5);
	if(t>1)
		t=1;
	if(t<0)
		t=0;
	return mat1->Shade(ray,hit,dirToLight,lightColor)*t + mat2->Shade(ray,hit,dirToLight,lightColor)*(1-t);
}

d_Vec3f Noise::getDiffuseColor(d_Vec3f wc) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(wc,1);
	double t = (PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves)+1)/2;
	return mat1->getDiffuseColor(wc)*t + mat2->getDiffuseColor(wc)*(1-t);
}

d_Vec3f Noise::getReflectiveColor(d_Vec3f wc) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(wc,1);
	double t = (PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves)+1)/2;
	return mat1->getReflectiveColor(wc)*t + mat2->getReflectiveColor(wc)*(1-t);
}

Marble::Marble(d_Mat4f *m, Material *mat1, Material *mat2, int octaves, float frequency, float amplitude)
{
	trans = *m;
	this->mat1 = mat1;
	this->mat2 = mat2;
	this->octaves = octaves;
	this->frequency = frequency;
	this->amplitude = amplitude;
}

d_Vec3f Marble::Shade(const Ray &ray, const Hit &hit, const d_Vec3f &dirToLight, const d_Vec3f &lightColor) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(ray.pointAtParameter(hit.getT()),1);
	float tmp = PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves);
	double t = (sin(tmp*amplitude+frequency*p[0])+1)/2;
	return mat1->Shade(ray,hit,dirToLight,lightColor)*t + mat2->Shade(ray,hit,dirToLight,lightColor)*(1-t);
}

d_Vec3f Marble::getDiffuseColor(d_Vec3f wc) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(wc,1);
	double t = (sin(PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves)*amplitude+frequency*p[0])+1)/2;
	return mat1->getDiffuseColor(wc)*t + mat2->getDiffuseColor(wc)*(1-t);
}

d_Vec3f Marble::getReflectiveColor(d_Vec3f wc) const
{
	d_Mat4f mattmp = trans;
	d_Vec4f p = mattmp*d_Vec4f(wc,1);
	double t = (sin(PerlinNoise::octave_noise(d_Vec3f(p[0],p[1],p[2]),octaves)*amplitude+frequency*p[0])+1)/2;
	return mat1->getReflectiveColor(wc)*t + mat2->getReflectiveColor(wc)*(1-t);
}
